import {randomString} from '../utils/utils'
import {useEffect, useRef} from "react";

type EventName = string;
type Id = string;
type Callback<T> = (event: T) => any;

export interface IEventBus {
  // interface无法定义私有属性
  // subscriptions: { [key: EventName]: { [key: Id]: Callback } },
  // putSubscription: (eventName: EventName, id: Id, callback: Callback) => void,
  // cancelSubscription: (eventName: EventName, id: Id) => void,
  on: (eventName: EventName) => Listener<any>,
  fire: <T>(eventName: EventName, eventInstance: T) => void,
}

/**
 * 监听/订阅工具
 *
 * 用法
 *
 * 监听
 * const listener = EventBus.on(eventName).listen(e => {
 *
 * });
 *
 * 发布
 * EventBus.fire(evntName, eventClassInstance);
 *
 * 取消监听：页面/组件销毁需要
 * listener.cancel();
 *
 */
class EventBusClass implements IEventBus {

  public subscriptions = {};

  /**
   * 将监听器放置到subscriptions
   * @param eventName 监听的事件
   * @param id 监听器的id
   * @param callback 回调函数
   */
  private putSubscription<T>(eventName: EventName, id: Id, callback: Callback<T>) {
    if (this.subscriptions[eventName]) {
      this.subscriptions[eventName][id] = callback;
      return;
    }
    this.subscriptions[eventName] = {[id]: callback}
  }

  /**
   * 取消订阅
   * @param eventName 监听的事件
   * @param id 监听器的id
   */
  private cancelSubscription(eventName: EventName, id: Id) {
    const eventObj = this.subscriptions[eventName];
    if (!eventObj) {
      console.error(`Cancel a not exist eventName "${eventName}"`);
      return;
    }
    if (!eventObj[id]) {
      console.error(`Cancel a not exist Listener "${id}" of eventName "${eventName}"`);
      return;
    }
    delete eventObj[id];
    if (Object.keys(eventObj).length === 0) {
      delete this.subscriptions[eventName];
    }
  }

  /**
   * 基于某事件，准备添加监听器
   * @param eventName 字符串类型的事件名
   * @returns {Listener} 监听器
   */
  public on<T>(eventName: string) {
    if (!eventName) {
      throw 'EventBus.on() must pass a not empty string';
    }
    return new Listener<T>(this.subscriptions, eventName, this.putSubscription, this.cancelSubscription);
  }

  /**
   * 针对某一事件发布
   * @param eventName 字符串类型的事件
   * @param eventInstance 作为回调函数的参数
   */
  public fire<T>(eventName: EventName, eventInstance: T) {
    const eventObj = this.subscriptions[eventName];
    if (!eventObj) {
      console.error(`Can\'t fire eventName "${eventName}" that not exist`);
      return;
    }
    for (const idKey in eventObj) {
      eventObj[idKey](eventInstance);
    }
  }
}

class Listener<T> {
  subscriptions: any;
  eventName: any;
  putSubscription: any;
  cancelSubscription: any;
  id: string;

  constructor(subscriptions, eventName, putSubscription, cancelSubscription) {
    this.subscriptions = subscriptions;
    this.eventName = eventName;
    this.putSubscription = putSubscription;
    this.cancelSubscription = cancelSubscription;
    this.id = new Date().getTime() + randomString(6);
  }

  //监听
  public listen(callback: Callback<T>) {
    if (!callback) {
      console.error('callback must not be none')
    }
    this.putSubscription(this.eventName, this.id, callback);
    return this;
  }

  //取消监听
  public cancel() {
    this.cancelSubscription(this.eventName, this.id);
  }
}

const EventBus = new EventBusClass();

export default EventBus;


/**
 * 进入组件/页面创建监听hook
 * @param eventName EventClass.eventName
 * @param callbackFunc 监听到消息的处理函数
 */
export const useEventBusSubscribeOnLoad = <T>(eventName: string, callbackFunc: Callback<T>) => {
  const listenerRef = useRef<any>();
  useEffect(() => {
    listenerRef.current = EventBus.on<T>(eventName).listen(callbackFunc);
    return () => {
      listenerRef.current?.cancel();
    }
  }, []);
}

/**
 * 按条件创建监听hook
 * @param eventName
 * @return (callbackFunc: Callback<T>) => void 执行创建的监听的函数，参数为"监听到消息的处理函数"，与useEventBusSubscribeOnLoad的callbackFunc具有同样的意义
 *
 * 注意：接收返回的函数时，请使用setXXXListener的形式，保持与官方hook的使用习惯一致
 */
export const useEventBusSubscribeDelay = <T>(eventName: string): (callbackFunc: Callback<T>) => void => {
  const listenerRef = useRef<any>();
  useEffect(() => {
    return () => {
      listenerRef.current?.cancel();
    }
  }, []);
  return (callbackFunc: Callback<T>) => {
    if (!listenerRef.current) listenerRef.current = EventBus.on<T>(eventName).listen(callbackFunc);
  }
}
